﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;

namespace InfoPathHelpers.Cryptography
{
    public static class PasswordHashAlgorithm
    {
        private const int SALT_LENGTH = 24;
        private const int HASH_LENGTH = 24;
        private const int PBKDF2_ITERATIONS = 1000;

        private const char DELIMITER = ':';
        private const int ITERATIONS_INDEX = 0;
        private const int SALT_INDEX = 1;
        private const int HASH_INDEX = 2;

        public static string CreateHash(string password)
        {
            var CSPRNG = new RNGCryptoServiceProvider();
            byte[] Salt = new byte[SALT_LENGTH];
            CSPRNG.GetBytes(Salt);

            return CreateHash(password, Salt);
        }

        public static string CreateHash(string password, string salt)
        {
            byte[] Salt = Convert.FromBase64String(salt);
            
            return CreateHash(password, Salt);
        }

        private static string CreateHash(string password, byte[] salt)
        {
            byte[] Hash = PBKDF2(password, salt, PBKDF2_ITERATIONS, HASH_LENGTH);

            return string.Join(DELIMITER.ToString(), new string[] 
            {   
                PBKDF2_ITERATIONS.ToString(),
                Convert.ToBase64String(salt),
                Convert.ToBase64String(Hash)
            });
        }

        public static bool ValidatePassword(string password, string passwordHash)
        {
            string[] HashSplit = passwordHash.Split(new Char[] { DELIMITER });
            int Iterations = int.Parse(HashSplit[ITERATIONS_INDEX]);
            byte[] Salt = Convert.FromBase64String(HashSplit[SALT_INDEX]);
            byte[] OriginalHash = Convert.FromBase64String(HashSplit[HASH_INDEX]);

            byte[] ComparisonHash = PBKDF2(password, Salt, Iterations, OriginalHash.Length);
            return SlowEquals(OriginalHash, ComparisonHash);
        }

        private static bool SlowEquals(byte[] a, byte[] b)
        {
            var Diff = (uint)a.Length ^ (uint)b.Length;
            for (int i = 0; i < a.Length && i < b.Length; i++)
            {
                Diff |= (uint)(a[i] ^ b[i]);
            }
            return Diff == 0;
        }

        private static byte[] PBKDF2(string password, byte[] salt, int iterations, int keyLength)
        {
            var PBKDF2 = new Rfc2898DeriveBytes(password, salt, iterations);
            return PBKDF2.GetBytes(keyLength);
        }
    }
}
